package com.symaster.gameengine.e2d;

import com.symaster.common.geom.util.PointUtil;
import com.symaster.gameengine.core.Component;
import com.symaster.gameengine.core.FboComponent;
import com.symaster.gameengine.core.Renderer;
import com.symaster.gameengine.e2d.transform.Transform;
import com.symaster.gameengine.util.SceneUtil;
import org.lwjgl.opengl.GL11;

import java.awt.geom.Rectangle2D;
import java.util.*;

import static org.lwjgl.opengl.GL11.glViewport;

/**
 * @author yinmiao
 * @version 2023/11/15
 */
public class Camera extends FboComponent {

    /**
     * 渲染的层
     */
    private final Set<Integer> drawLayers;
    /**
     * 摄像头视场角(越大看到的东西越多)
     */
    public float fov = 120f;
    /**
     * 摄像头距离(越远看到的东西越多)
     */
    public float length = 240f;
    /**
     * 相机的尺寸(越大看到的东西越多)
     */
    private float cameraSize = 10f;
    /**
     * 摄像头渲染位置(在屏幕上的位置)
     */
    private int paintX = 0, paintY = 0, paintWidth = -1, paintHeight = -1;
    /**
     * 设置相机的宽高比
     */
    private float aspectRatio;
    /**
     * 设定相机的渲染场景
     */
    private Scene renderScene;

    public Camera() {
        drawLayers = new LinkedHashSet<>();
        drawLayers.addAll(defaultLayers());
    }

    protected Collection<Integer> defaultLayers() {
        return Collections.emptyList();
    }

    public float getAspectRatio() {
        return aspectRatio;
    }

    public void setAspectRatio(float aspectRatio) {
        this.aspectRatio = aspectRatio;
    }

    public Scene getRenderScene() {
        return renderScene;
    }

    public void setRenderScene(Scene renderScene) {
        this.renderScene = renderScene;
    }

    /**
     * @return 返回当前显示场景的区域
     */
    public Rectangle2D.Float getSceneDisplayRect() {
        return getSceneDisplayRect(this.aspectRatio);
    }

    /**
     * openGl draw
     */
    @Override
    public void repaint2Fbo() {
        // 绑定当前Fbo
        this.bindFbo();
        this.clear();
        this.bindDefaultBuffer();

        // 当前相机将要绘制场景中的区域
        Rectangle2D.Float sceneDisplayRect = getSceneDisplayRect();

        // 初始化Fbo
        this.setupFBO((int) sceneDisplayRect.width, (int) sceneDisplayRect.height);

        // 过滤出相机可视范围内的所有渲染组件
        List<Renderer> renderers = this.getRenderers(renderScene, sceneDisplayRect);
        if (renderers.isEmpty()) {
            return;
        }

        // 调用所有渲染器进行渲染
        this.drawRenders(renderers);
    }

    public float getCameraSize() {
        return cameraSize;
    }

    public void setCameraSize(float cameraSize) {
        this.cameraSize = cameraSize;
    }

    public int getPaintX() {
        return paintX;
    }

    public void setPaintX(int paintX) {
        this.paintX = paintX;
    }

    public int getPaintY() {
        return paintY;
    }

    public void setPaintY(int paintY) {
        this.paintY = paintY;
    }

    public int getPaintWidth() {
        return paintWidth;
    }

    public void setPaintWidth(int paintWidth) {
        this.paintWidth = paintWidth;
    }

    public int getPaintHeight() {
        return paintHeight;
    }

    public void setPaintHeight(int paintHeight) {
        this.paintHeight = paintHeight;
    }

    public float getFov() {
        return fov;
    }

    public void setFov(float fov) {
        this.fov = fov;
    }

    public float getLength() {
        return length;
    }

    public void setLength(float length) {
        this.length = length;
    }

    public Set<Integer> getDrawLayers() {
        return drawLayers;
    }

    private void drawRenders(List<Renderer> renderers) {
        // 当前相机在场景中的坐标
        Transform sceneTransform = SceneUtil.toSceneTransform(this);

        float centerX = this.getFboWidth() / 2f;
        float centerY = this.getFboHeight() / 2f;

        for (Renderer renderer : renderers) {
            // 让渲染器渲染
            renderer.repaint2Fbo();

            // 设置颜色, 用来降低亮度
            float brightness = renderer.findBrightness();
            GL11.glColor4f(brightness, brightness, brightness, renderer.findAlpha());

            // 将渲染器渲染到当前Fbo
            this.drawRender(renderer, sceneTransform, centerX, centerY);

            // 恢复颜色
            GL11.glColor4f(1, 1, 1, 1);
        }
    }

    private void drawRender(Renderer renderer, Transform sceneTransform, float centerX, float centerY) {
        // 渲染器的场景坐标
        Transform renderSceneTransform = SceneUtil.toSceneTransform(renderer);

        float vX = renderSceneTransform.getX() - sceneTransform.getX();
        float vY = renderSceneTransform.getY() - sceneTransform.getY();

        int x = Math.round(vX - (renderer.getFboWidth() / 2f) + centerX);
        int y = Math.round(vY - (renderer.getFboHeight() / 2f) + centerY);
        int width = renderer.getFboWidth();
        int height = renderer.getFboHeight();

        // 绑定当前Fbo
        bindFbo();
        // 设置当前渲染器的绘制区域
        glViewport(x, y, width, height);
        // 绘制渲染器的纹理
        renderer.renderTextureToScreen();
        // 绑定默认缓冲区
        bindDefaultBuffer();
    }

    private List<Renderer> getRenderers(Scene scene, Rectangle2D.Float sceneDisplayRect) {
        float displayX1 = sceneDisplayRect.x;
        float displayY1 = sceneDisplayRect.y;
        float displayX2 = sceneDisplayRect.x + sceneDisplayRect.width;
        float displayY2 = sceneDisplayRect.y + sceneDisplayRect.height;

        Set<Integer> drawLayers = getDrawLayers();

        // 当前渲染组件在场景中的坐标
        return scene.getComponents(Renderer.class, true).stream()
                .filter(e -> {
                    if (!drawLayers.contains(e.getLayer())) {
                        return false;
                    }

                    if (!e.isVisible()) {
                        return false;
                    }

                    if (e.getWidth() <= 0 || e.getHeight() <= 0) {
                        return false;
                    }

                    // 当前渲染组件在场景中的坐标
                    Transform renderSceneTransform = SceneUtil.toSceneTransform(e);
                    float renderWidth = e.getWidth();
                    float renderHeight = e.getHeight();

                    float x1 = renderSceneTransform.getX() - renderWidth / 2f;
                    float y1 = renderSceneTransform.getY() - renderHeight / 2f;
                    float x2 = renderSceneTransform.getX() + renderWidth / 2f;
                    float y2 = renderSceneTransform.getY() + renderHeight / 2f;

                    return !(x1 > displayX2) && !(x2 < displayX1) &&
                            !(y1 > displayY2) && !(y2 < displayY1);
                })
                .sorted(Comparator.comparingInt(Component::getLayer))
                .toList();
    }

    /**
     * @return 当前相机将要绘制场景中的区域
     */
    private Rectangle2D.Float getSceneDisplayRect(float aspectRatio) {
        Transform sceneTransformCam = SceneUtil.toSceneTransform(this);

        float displayWidth = (float) calculateSideLength(this.cameraSize);
        float displayHeight = displayWidth / aspectRatio;

        return new Rectangle2D.Float(sceneTransformCam.getX() - displayWidth / 2, sceneTransformCam.getY() - displayHeight / 2, displayWidth, displayHeight);
    }

    private double calculateSideLength(float n) {
        double[] x1 = PointUtil.rotateTransform(0, -length, 0, 0, -fov);
        double[] x2 = PointUtil.rotateTransform(n, -length, n, 0, fov);
        return x2[0] - x1[0];
    }
}
